
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include "../iface/debug.h"
#include "../utils/BaseConvert.h"
#include "../utils/StringUtils.h"
#include "RSAPublicKey.h"
#include "RSAPrivateKey.h"

/** Constructor for a blank RSA private key. */
RSAPrivateKey::RSAPrivateKey()
{
   key = NULL;
   keyname = NULL;
}

/** Constructor for a blank RSA private key with a name.
 * 
 * @param name The name to attach to the blank key. */
RSAPrivateKey::RSAPrivateKey(const char *name)
{
   key = NULL;
   keyname = NULL;
   setKeyName(name);
}

/** Copy constructor for an RSA private key.
 * 
 * @param other The RSA private key to copy from. */
RSAPrivateKey::RSAPrivateKey(const RSAPrivateKey &other)
{
   key = RSA_new();
   if (key == NULL)
   {
      errprint("RSA_new failed");
      return;
   }
   copyRSASSLPrivateKey(key, other.key);
   keyname = NULL;
   setKeyName(other.keyname);
}

/** Copy assignment operator for an RSA private key.
 * 
 * @param other The RSA private key to copy from. */
RSAPrivateKey &RSAPrivateKey::operator=(const RSAPrivateKey &other)
{
   key = RSA_new();
   if (key == NULL)
   {
      errprint("RSA_new failed");
      return *this;
   }
   copyRSASSLPrivateKey(key, other.key);
   keyname = NULL;
   setKeyName(other.keyname);
   return *this;
}

/** Construct the RSA private key from a PEM encoded PKCS #8 text
 * format RSA private key.
 * 
 * @param pemtext The PEM encoded PKCS #8 text that represents an RSA
 * private key, from which this key is to be constructed.
 * 
 * @param name The name to be attached to the key, once
 * constructed. */
RSAPrivateKey::RSAPrivateKey(const char *pemtext, const char *name)
{
   importKeyFromASCII(pemtext, name);
}

/** Construct the RSA private key from an encrypted and passworded PEM
 * encoded PKCS #8 text format RSA private key.
 * 
 * @param pemtext The PEM encoded PKCS #8 text that represents an RSA
 * public key, from which this key is to be constructed.
 * 
 * @param password The password to decrypt the PEM encoded PKCS #8
 * text that represents the RSA public key.
 * 
 * @param name The name to be attached to the key, once
 * constructed. */
RSAPrivateKey::RSAPrivateKey(const char *pemtext, const char *password, const char *name)
{
   importKeyFromASCIIWithPassword(pemtext, password, name);
}

/** Construct the RSA private key from an RSA private key in SSL
 * format.
 * 
 * @param prvkey The SSL format RSA private key to construct this key
 * from.
 * 
 * @param name The name to be attached to the key, once
 * constructed. */
RSAPrivateKey::RSAPrivateKey(RSA *prvkey, const char *name)
{
   key = RSA_new();
   if (key == NULL)
   {
      errprint("RSA_new failed");
      return;
   }
   copyRSASSLPrivateKey(key, prvkey);
   keyname = NULL;
   setKeyName(name);
}

/** Destructor for the RSA private key. */
RSAPrivateKey::~RSAPrivateKey()
{
   if (key) RSA_free(key);
   key = NULL;
   if (keyname) free((void *)keyname);
   keyname = NULL;
}

/** Get a copy of the private key as an SSL format object.
 * 
 * @param[out] prvkey A pointer to a place to put a pointer to the new
 * SSL format RSA private key which is a copy of this key. The
 * responsibility for freeing the new RSA in SSL format is transferred
 * to the caller.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::getSSLKeyComponents(RSA **prvkey) const
{
   *prvkey = RSA_new();
   if ((*prvkey) == NULL)
   {
      errprint("RSA_new failed");
      return -1;
   }
   return copyRSASSLPrivateKey(*prvkey, key);
}

/** Get a copy of the private key in PEM encoded PKCS #8 text format.
 * 
 * @param[out] pemtext A pointer to a place to put a pointer to the
 * exported key in PEM encoded PKCS #8 text format. The responsibility
 * for freeing the new string is transferred to the caller.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::getPEMKeyData(char **pemtext) const
{
   return getPEMKeyData(pemtext, NULL);
}

/** Get a copy of the private key in PEM encoded and encrypted PKCS #8
 * text format, protected with the given password.
 * 
 * @param[out] pemtext A pointer to a place to put a pointer to the
 * exported key in encrypted PEM encoded PKCS #8 text format. The
 * responsibility for freeing the new string is transferred to the
 * caller.
 * 
 * @param password The password to encrypt and decrypt the key with.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::getPEMKeyData(char **pemtext, const char *password) const
{
   char *data;
   int damt;
   int retval = 0;
   BIO *bo = BIO_new(BIO_s_mem());
   if (!PEM_ASN1_write_bio((i2d_of_void *)i2d_RSAPrivateKey, PEM_STRING_RSA, bo, (char *)key, (password) ? EVP_des_ede3_cbc() : NULL, NULL, 0, 0, (void *)password))
   {
      retval = 1;
      errprint("could not export private key (password optional)");
      SSLCheckError();
      goto bioshutdown;
   }
   SSLCheckError();
   damt = BIO_get_mem_data(bo, &data);
   (*pemtext) = (char *)malloc(sizeof(char)*(damt+1));
   memcpy((*pemtext), data, sizeof(char)*(damt+1));
   (*pemtext)[damt] = '\0';
bioshutdown:
   BIO_set_close(bo, BIO_CLOSE);
   BIO_free(bo);
   return retval;
}

/** Compare this PrivateKey to a PublicKey, and see if they are part
 * of the same key pair (one decrypts any content encrypted by the
 * other).
 * 
 * @param pubkey The public key to compare to this private key to see
 * if they make a valid pair.
 * 
 * @return 1 if they make a matching pair of keys, 0 if they do not,
 * and negative values represent errors. */
int RSAPrivateKey::compareKeyBelongs(PublicKey *pubkey) const
{
   if (pubkey == NULL)
   {
      errprint("comparing against NULL key");
      return 0;
   }
   switch (pubkey->getKeyType())
   {
      case KEY_TYPE_PUBLIC_RSA:
      {
         RSA *rsaKeyOther = NULL;
         ((RSAPublicKey *)pubkey)->getSSLKeyComponents(&rsaKeyOther);
         EVP_PKEY *evp1 = EVP_PKEY_new();
         EVP_PKEY *evp2 = EVP_PKEY_new();
         EVP_PKEY_set1_RSA(evp1, key);
         EVP_PKEY_set1_RSA(evp2, rsaKeyOther);
         int retval = EVP_PKEY_cmp(evp1, evp2);
         EVP_PKEY_free(evp1);
         EVP_PKEY_free(evp2);
         RSA_free(rsaKeyOther);
         SSLCheckError();
         switch (retval)
         {
            case -2:
               errprint("key compare operation not supported (EVP_PKEY_cmp())");
               break;
            case -1:
               errprint("tried to compare different key types (EVP_PKEY_cmp())");
               return 0;
            case 0:
               return 0;
            case 1:
               return 1;
         }
         return 0;
         break;
      }
      default:
      {
         return 0;
         break;
      }
   }
}

/** Copy an SSL format private key.
 * 
 * @param[out] dst Pointer to an initialised blank SSL format RSA key
 * to copy into.
 * 
 * @param src Pointer to an existing SSL format RSA private key to
 * copy from.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::copyRSASSLPrivateKey(RSA *dst, RSA *src)
{
   if (dst == NULL) { errprint("NULL private key destination"); return -1; }
   if (src == NULL) { errprint("NULL private key source"); return 1; }
   // Copy public key
   dst->n = BN_new();
   if ((src->n == NULL) || (BN_copy(dst->n, src->n) != (dst->n)))
   {
      errprint("Could not copy%s pub->n", (src->n) ? "" : " NULL");
      return -1;
   }
   dst->e = BN_new();
   if ((src->e == NULL) || (BN_copy(dst->e, src->e) != (dst->e)))
   {
      errprint("Could not copy%s pub->e", (src->e) ? "" : " NULL");
      return -1;
   }

   // Copy private key
   dst->d = BN_new();
   if ((src->d == NULL) || (BN_copy(dst->d, src->d) != (dst->d)))
   {
      errprint("Could not copy%s prv->d", (src->d) ? "" : " NULL");
      return -1;
   }
   dst->p = BN_new();
   if ((src->p == NULL) || (BN_copy(dst->p, src->p) != (dst->p)))
   {
      errprint("Could not copy%s prv->p", (src->p) ? "" : " NULL");
      return -1;
   }
   dst->q = BN_new();
   if ((src->q == NULL) || (BN_copy(dst->q, src->q) != (dst->q)))
   {
      errprint("Could not copy%s prv->q", (src->q) ? "" : " NULL");
      return -1;
   }

   dst->dmp1 = BN_new();
   if ((src->dmp1 == NULL) || (BN_copy(dst->dmp1, src->dmp1) != (dst->dmp1)))
   {
      errprint("Could not copy%s prv->dmp1", (src->dmp1) ? "" : " NULL");
      return -1;
   }
   dst->dmq1 = BN_new();
   if ((src->dmq1 == NULL) || (BN_copy(dst->dmq1, src->dmq1) != (dst->dmq1)))
   {
      errprint("Could not copy%s prv->dmq1", (src->dmq1) ? "" : " NULL");
      return -1;
   }
   dst->iqmp = BN_new();
   if ((src->iqmp == NULL) || (BN_copy(dst->iqmp, src->iqmp) != (dst->iqmp)))
   {
      errprint("Could not copy%s prv->iqmp", (src->iqmp) ? "" : " NULL");
      return -1;
   }
   return 0;
}

/** Import and name an RSA private key from an RSA private key in
 * encrypted PEM format PKCS #8.
 * 
 * @param pemtext The PEM encoded PKCS #8 text that represents an RSA
 * public key, from which this key is to be constructed.
 * 
 * @param password The password to decrypt the PEM encoded PKCS #8
 * text that represents the RSA private key.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::importPrivateKeyWithOptionalPassword(const char *pemtext, const char *password)
{
   if (!key) key = RSA_new();
   int retval = 0;
   BIO *bo = BIO_new_mem_buf((void *)pemtext, -1);
   if (!PEM_ASN1_read_bio((d2i_of_void *)d2i_RSAPrivateKey, PEM_STRING_RSA, bo, (void **)(&key), 0, (void *)password))
   {
      retval = 1;
      errprint("could not import private key (password optional)");
      SSLCheckError();
      goto bioshutdown;
   }
   SSLCheckError();
bioshutdown:
   BIO_set_close(bo, BIO_CLOSE);
   BIO_free(bo);
   return retval;
}

/** Import an RSA private key from some PEM format PKCS #8 text.
 * 
 * @param text The PEM encoded PKCS #8 text that represents an RSA
 * public key, from which this key is to be constructed.
 * 
 * @param name The name to be attached to the key.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::importKeyFromASCII(const char *text, const char *name)
{
   return importKeyFromASCIIWithPassword(text, NULL, name);
}

/** Import and name an RSA private key from an RSA private key in
 * encrypted PEM format PKCS #8.
 * 
 * @param text The PEM encoded PKCS #8 text that represents an RSA
 * public key, from which this key is to be constructed.
 * 
 * @param password The password to decrypt the PEM encoded PKCS #8
 * text that represents the RSA private key.
 * 
 * @param name The name to be attached to the key.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::importKeyFromASCIIWithPassword(const char *text, const char *password, const char *name)
{
   setKeyName(name);
   int retval = importPrivateKeyWithOptionalPassword(text, password);
   return retval;
}

/** Export an RSA private key to some PEM format PKCS #8 text.
 * 
 * @param[out] text A pointer to a place to put a pointer to the
 * exported key in PEM encoded PKCS #8 text format. The responsibility
 * for freeing the new string is transferred to the caller.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::exportKeyToASCII(char **text) const
{
   return getPEMKeyData(text, NULL);
}

/** Export an RSA private key to some PEM format PKCS #8 text
 * encrypted with the given password.
 * 
 * @param[out] text A pointer to a place to put a pointer to the
 * exported key in encrypted PEM encoded PKCS #8 text format. The
 * responsibility for freeing the new string is transferred to the
 * caller.
 * 
 * @param password The password to encrypt and decrypt the key with.
 * 
 * @return Returns zero on success, non-zero on failure. */
int RSAPrivateKey::exportKeyToASCIIWithPassword(char **text, const char *password) const
{
   return getPEMKeyData(text, password);
}
